package com.biz.primus.ms.coupon.dao.redis;

import com.biz.primus.base.exception.utils.AssertUtils;
import com.biz.primus.base.redis.CrudRedisDao;
import com.biz.primus.base.redis.util.RedisUtil;
import com.biz.primus.model.coupon.enums.CouponAcquireApproach;
import com.biz.primus.model.coupon.enums.CouponState;
import com.biz.primus.ms.coupon.dao.ro.CouponRo;
import com.google.common.collect.Lists;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Repository;

import static com.biz.primus.model.coupon.exceptions.CouponExceptionType.COUPON_INSUFFICIENT;

/**
 * 优惠券RedisDao
 *
 * @author david-liu
 */
@Repository
public class CouponRedisDao extends CrudRedisDao<CouponRo, Long> {

    // 判断传入的优惠券是否归属于某个用户
    public boolean isCouponBelong2User(Long userId, Long couponId) {
        String userNotUsedCouponListKey = this.getUserNotUsedCouponListKey(userId);
        List<Long> couponIds = Optional.ofNullable(super.lrange(userNotUsedCouponListKey, 0, -1)).orElse(Lists.newArrayList()).stream().map(RedisUtil::byteArrayToLong).collect(Collectors.toList());
        return couponIds.contains(couponId);
    }

    // 添加优惠券到优惠券类型未领取优惠券列表当中
    public void appendCouponType2BeFetchedCoupons(Long couponTypeId, Collection<Long> couponIds) {
        String couponType2BeFetchedCouponsListKey = this.getCouponType2BeFetchedCouponsListKey(couponTypeId);
        List<byte[]> couponIdBytes = couponIds.stream().map(RedisUtil::toByteArray).collect(Collectors.toList());
        super.pipeLpush(RedisUtil.toByteArray(couponType2BeFetchedCouponsListKey), couponIdBytes.toArray(new byte[couponIdBytes.size()][]));
    }

    // 获取指定优惠券类型可领取优惠券数量
    public Integer getCouponTypeAvailableCouponCount(Long couponTypeId) {
        String couponType2BeFetchedCouponsListKey = this.getCouponType2BeFetchedCouponsListKey(couponTypeId);
        return Math.toIntExact(super.llen(couponType2BeFetchedCouponsListKey));
    }

    // 添加优惠券到用户未使用优惠券的List中
    public List<CouponRo> appendCoupon2UserNotUsedCouponList(List<Long> userIds, Long couponTypeId, CouponAcquireApproach couponAcquireApproach) {
        String couponType2BeFetchedCouponsListKey = this.getCouponType2BeFetchedCouponsListKey(couponTypeId);
        List<CouponRo> couponRos = super.rpopFromList(couponType2BeFetchedCouponsListKey, userIds.size());
        AssertUtils.isTrue(couponRos.size() == userIds.size(), COUPON_INSUFFICIENT, new Object[]{String.format("取出了%s张优惠券, 目标需要发送%s张优惠券", couponRos.size(), userIds.size())});
        Timestamp couponUpdateTimestamp = new Timestamp(System.currentTimeMillis());
        List<byte[]> userNotUsedCouponListKeyBytes = Lists.newArrayList();
        List<byte[]> userNotUserCouponListValueBytes = Lists.newArrayList();
        IntStream.range(0, userIds.size()).forEach(i -> {
            Long userId = userIds.get(i);
            CouponRo couponRo = couponRos.get(i);
            couponRo.setUserId(userId);
            couponRo.setUsed(false);
            couponRo.setUpdateTimestamp(couponUpdateTimestamp);
            couponRo.setCouponAcquireApproach(couponAcquireApproach);
            couponRo.setCouponState(CouponState.UNENFORCED);
            userNotUsedCouponListKeyBytes.add(RedisUtil.toByteArray(this.getUserNotUsedCouponListKey(userId)));
            userNotUserCouponListValueBytes.add(RedisUtil.toByteArray(couponRo.getId()));
        });
        this.save(couponRos);
        super.pipeLpush(userNotUsedCouponListKeyBytes, userNotUserCouponListValueBytes);
        return couponRos;
    }

    // 使用用户的优惠券
    public void useUserCoupon(Long userId, Long couponId) {
        String userNotUsedCouponListKey = this.getUserNotUsedCouponListKey(userId);
        super.lrem(userNotUsedCouponListKey, 1, RedisUtil.toByteArray(couponId));
        String userUsedCouponListKey = this.getUserUsedCouponListKey(userId);
        super.lpush(userUsedCouponListKey, RedisUtil.toByteArray(couponId));
    }

    // 列出用户未使用优惠券
    public List<CouponRo> listUserNotUsedCoupons(Long userId) {
        String userNotUsedCouponListKey = this.getUserNotUsedCouponListKey(userId);
        List<Long> couponIds = Optional.ofNullable(super.lrange(userNotUsedCouponListKey, 0, -1)).orElse(Lists.newArrayList()).stream().map(RedisUtil::byteArrayToLong).collect(Collectors.toList());
        return CollectionUtils.isNotEmpty(couponIds) ? super.findByIds(couponIds) : Lists.newArrayList();
    }

    // 列出用户已使用优惠券
    public List<CouponRo> listUserUsedCoupons(Long userId) {
        String userUsedCouponListKey = this.getUserUsedCouponListKey(userId);
        List<Long> couponIds = Optional.ofNullable(super.lrange(userUsedCouponListKey, 0, -1)).orElse(Lists.newArrayList()).stream().map(RedisUtil::byteArrayToLong).collect(Collectors.toList());
        return CollectionUtils.isNotEmpty(couponIds) ? super.findByIds(couponIds) : Lists.newArrayList();
    }

    // 列出用户已过期优惠券
    public List<CouponRo> listUserExpiredCoupons(Long userId) {
        String userExpiredCouponListKey = this.getUserExpiredCouponListKey(userId);
        List<Long> couponIds = Optional.ofNullable(super.lrange(userExpiredCouponListKey, 0, -1)).orElse(Lists.newArrayList()).stream().map(RedisUtil::byteArrayToLong).collect(Collectors.toList());
        return CollectionUtils.isNotEmpty(couponIds) ? super.findByIds(couponIds) : Lists.newArrayList();
    }

    // 某种类型优惠券等待领取的优惠券List
    private String getCouponType2BeFetchedCouponsListKey(Long couponTypeId) {
        return super.getKeyByParams("couponType", couponTypeId, "toBeFetchedCoupons");
    }

    // 用户未使用优惠券List Key
    private String getUserNotUsedCouponListKey(Long userId) {
        return getKeyByParams("user", userId, "unused", "list");
    }

    // 用户已使用优惠券List Key
    private String getUserUsedCouponListKey(Long userId) {
        return getKeyByParams("user", userId, "used", "list");
    }

    // 用户已过期优惠券List Key
    private String getUserExpiredCouponListKey(Long userId) {
        return getKeyByParams("user", userId, "expired", "list");
    }
}
